package org.fujene.structure;

import org.fujene.constants.Constants;

public final class LookupPointer implements Comparable<LookupPointer>{
	// public byte flags;
	public int block;
	public int point;
	public long firstID;
	public int termfreq;
	
	/**
	 * It represents (-1, -1), with ID: -1 and termfreq: -1.
	 */
	public static final LookupPointer EMPTY = new LookupPointer();
	/**
	 * It represents (-128, -1), with ID: -1 and termfreq: -1.
	 */
	public static final LookupPointer EMPTY_RELATIVE = new LookupPointer(-128, -1);

	public LookupPointer(){
		this.block = -1;
		this.point = -1;
		this.firstID = -1L;
		this.termfreq = -1;
	}

	// copy constructor
	public LookupPointer(LookupPointer ptr){
		this.block = ptr.block;
		this.point = ptr.point;
		this.firstID = ptr.firstID;
		this.termfreq = ptr.termfreq;
	}

	public LookupPointer(int block, int point){
		this(block, point, -1L);
	}

	public LookupPointer(int block, int point, long ID){
		this(block, point, ID, -1);
	}

	public LookupPointer(int block, int point, long ID, int termfreq){
		this.block = block;
		this.point = point;
		this.firstID = ID;
		this.termfreq = termfreq;
	}

	public LookupPointer(int block, int point, int termfreq){
		this.block = block;
		this.point = point;
		this.termfreq = termfreq;
	}
	
	public static LookupPointer valueOf(int block, int point) {
		return new LookupPointer(block, point);
	}
	
	public LookupPointer modify(int block, int point) {
		this.block = block;
		this.point = point;
		return this;
	}
	
	public void modify(int block, int point, long ID) {
		this.block = block;
		this.point = point;
		this.firstID = ID;
	}

	public String toString(){
		return new String("(" + this.block + ", " + this.point + ")");
	}
	
	public String toStringEx() {
		return new String("(" + this.block + ", " + this.point + ")\nID: " + this.firstID + " freq.: " + this.termfreq);
	}

	public boolean equals(LookupPointer q){
		if(q.block == this.block && q.point == this.point) return true;
		return false;
	}
	
	public static LookupPointer fromBytes(byte[] b){
		return fromBytes(b, 0);
	}

	public static LookupPointer fromBytes(byte[] b, int from){
		if(from + Constants.BYTE_GLOBALPTR > b.length)
			throw new IndexOutOfBoundsException("Cannot convert byte array to pointer, length: " + b.length
					+ " from: " + from);
		LookupPointer newPtr = new LookupPointer();

		newPtr.block = byteToInt(b, from, Constants.BYTE_BLOCKPTR);
		newPtr.point = byteToInt(b, from + Constants.BYTE_BLOCKPTR, Constants.BYTE_LOCALPTR);
		newPtr.firstID = -1L;
		newPtr.termfreq = -1;
		
		return newPtr;
	}

	public static LookupPointer fromBytesShort(byte[] b){
		return fromBytesShort(b, 0);
	}

	public static LookupPointer fromBytesShort(byte[] b, int from){
		if(from + Constants.BYTE_RELATIVEPTR > b.length)
			throw new IndexOutOfBoundsException("Cannot convert byte array to pointer, length: " + b.length
					+ " from: " + from);
		LookupPointer newPtr = new LookupPointer();

		newPtr.block = byteToInt(b, from, Constants.BYTE_RELATIVEBLOCKPTR);
		newPtr.point = byteToInt(b, from + Constants.BYTE_RELATIVEBLOCKPTR, Constants.BYTE_LOCALPTR);
		newPtr.firstID = -1L;
		newPtr.termfreq = -1;

		return newPtr;
	}

	public static LookupPointer fromBytesLong(byte[] b){
		return fromBytesLong(b, 0);
	}

	public static LookupPointer fromBytesLong(byte[] b, int from){
		if(from + Constants.BYTE_GLOBALPTR + Constants.BYTE_TERMFREQ > b.length)
			throw new IndexOutOfBoundsException("Cannot convert byte array to pointer, length: " + b.length
					+ " from: " + from);
		LookupPointer newPtr = new LookupPointer();

		newPtr.block = byteToInt(b, from, Constants.BYTE_BLOCKPTR);
		newPtr.point = byteToInt(b, from + Constants.BYTE_BLOCKPTR, Constants.BYTE_LOCALPTR);
		newPtr.termfreq = byteToInt(b, from + Constants.BYTE_BLOCKPTR + Constants.BYTE_LOCALPTR,
				Constants.BYTE_TERMFREQ);
		newPtr.firstID = -1L;

		return newPtr;
	}

	public byte[] toBytes(){
		byte[] b = new byte[Constants.BYTE_GLOBALPTR];
		int valtmp;
		byte temp = 0;

		valtmp = this.block;
		for(int i = Constants.BYTE_BLOCKPTR - 1; i >= 0; i--){
			temp = (byte)(valtmp & 0xFF);
			b[i] = temp;
			valtmp >>= 8;
		}

		valtmp = this.point;
		for(int i = Constants.BYTE_GLOBALPTR - 1; i >= Constants.BYTE_BLOCKPTR; i--){
			temp = (byte)(valtmp & 0xFF);
			b[i] = temp;
			valtmp >>= 8;
		}

		return b;
	}

	public byte[] toBytesShort(){
		byte[] b = new byte[Constants.BYTE_RELATIVEPTR];
		int valtmp;
		byte temp = 0;

		valtmp = this.block;
		for(int i = Constants.BYTE_RELATIVEBLOCKPTR - 1; i >= 0; i--){
			temp = (byte)(valtmp & 0xFF);
			b[i] = temp;
			valtmp >>= 8;
		}

		valtmp = this.point;
		for(int i = Constants.BYTE_RELATIVEPTR - 1; i >= Constants.BYTE_RELATIVEBLOCKPTR; i--){
			temp = (byte)(valtmp & 0xFF);
			b[i] = temp;
			valtmp >>= 8;
		}

		return b;
	}

	public byte[] toBytesLong(){
		byte[] b = new byte[Constants.BYTE_GLOBALPTR + Constants.BYTE_TERMFREQ];
		int valtmp;
		byte temp = 0;

		valtmp = this.block;
		for(int i = Constants.BYTE_BLOCKPTR - 1; i >= 0; i--){
			temp = (byte)(valtmp & 0xFF);
			b[i] = temp;
			valtmp >>= 8;
		}

		valtmp = this.point;
		for(int i = Constants.BYTE_GLOBALPTR - 1; i >= Constants.BYTE_BLOCKPTR; i--){
			temp = (byte)(valtmp & 0xFF);
			b[i] = temp;
			valtmp >>= 8;
		}

		valtmp = this.termfreq;
		for(int i = Constants.BYTE_GLOBALPTR + Constants.BYTE_TERMFREQ - 1; i >= Constants.BYTE_GLOBALPTR; i--){
			temp = (byte)(valtmp & 0xFF);
			b[i] = temp;
			valtmp >>= 8;
		}

		return b;
	}

	private static int byteToInt(byte[] b, int from, int length){
		int temp = 0;
		if(length > 4 || length <= 0) length = 4;

		for(int i = from; i < from + length; i++){
			// Big endian
			temp <<= 8;
			temp += (b[i] & 0xff);
		}

		// ge complement
		if(temp >= 0 && ((temp & (1 << (length * 8 - 1))) != 0)){
			temp -= (1 << (length * 8));
		}

		return temp;
	}

	@Override
	public boolean equals(Object obj){
		if(obj instanceof LookupPointer) return equals((LookupPointer)obj);
		else return false;
	}

	@Override
	public int compareTo(LookupPointer query){
		if(query.block != this.block){
			return this.block - query.block;
		}else{
			if(query.point != this.point){
				return this.point - query.point;
			}else{
				return 0;
			}
		}
	}

	@Override
	public int hashCode(){
		return ((this.block << 16) + this.point);
	}

	@Override
	protected Object clone() throws CloneNotSupportedException{
		return new LookupPointer(this.block, this.point, this.firstID, this.termfreq);
	}
}
